package com.huawei.app.demo.util;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 网络工具类
 *
 * @since 2021-01-26
 */
public class NetworkUtils {

    /**
     * 获取Mac地址及IP信息<br/>
     * IdeaHub涉及的网卡及IP包括: 有线网络, Wi-Fi, 无线热点<br/>
     * @return 返回当前可用状态的Mac地址及IP, 如果在没有连接网线场景下也需要获取MAC地址, 可以参考此方法新写一个method
     */
    public static String getNetworkInfo() {
        StringBuilder result = new StringBuilder();

        Map<String, NetworkInfo> networkInfoMap = getNetworkInfoAsMap();
        Iterator<Map.Entry<String,NetworkInfo>> it = networkInfoMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String,NetworkInfo> entry = it.next();
            if (result.length() > 0) {
                result.append(System.lineSeparator());
            }
            result.append(entry.getKey()).append(":").append(entry.getValue().toString());
        }
        return result.toString();
    }

    /**
     * 获取设备IP
     * @param isWiFi true: WiFi IP, false: 有线IP
     * @return
     */
    public static String getDeviceIp(boolean isWiFi) {
        Map<String, NetworkInfo> networkInfoMap = getNetworkInfoAsMap();

        if (isWiFi) {
            // 获取WiFi IP
            NetworkInfo netInfo = networkInfoMap.get("wlan0");
            if (netInfo != null) {
                return netInfo.getIps().get(0);
            }
        } else {
            // 获取有线IP
            NetworkInfo netInfo = networkInfoMap.get("eth0.wlan");
            if (netInfo != null) {
                return netInfo.getIps().get(0);
            }
        }
        return "";
    }

    /**
     * 获取Mac地址及IP信息<br/>
     * IdeaHub涉及的网卡及IP包括: 有线网络, Wi-Fi, 无线热点<br/>
     * @return map key:networkName value:mac and ips
     */
    private static Map<String, NetworkInfo> getNetworkInfoAsMap() {
        Map<String, NetworkInfo> mapResult = new LinkedHashMap<>();
        try {
            Enumeration<NetworkInterface> networkInfos = NetworkInterface.getNetworkInterfaces();
            if (networkInfos == null) {
                return mapResult;
            }

            while (networkInfos.hasMoreElements()) {
                NetworkInterface networkInterface = networkInfos.nextElement();

                // 获取MAC地址
                byte[] macBytes = networkInterface.getHardwareAddress();
                if (macBytes == null || macBytes.length == 0) {
                    continue;
                }
                String mac = toMacStr(macBytes);

                // 获取IP地址
                NetworkInfo tmpNetInfos = new NetworkInfo(mac);
                Enumeration<InetAddress> ipEnumration = networkInterface.getInetAddresses();
                while (ipEnumration.hasMoreElements()) {
                    InetAddress ipAddress = ipEnumration.nextElement();
                    // 跳过ipv6, 环路地址, 本地连接地址
                    // isLoopbackAddress 表示环路地址, 主要包含以下网段 127.0.0.0 ~ 127.255.255.255
                    // isLinkLocalAddress 表示本地连接地址, 主要包含以下网段 169.254.0.0 ~ 169.254.255.255
                    // isSiteLocalAddress() 表示内网地址, 主要包含以下几个网段 10/8, 172.16/12, 192.168/16
                    // 详细描述参见以下链接
                    // https://www.huaweicloud.com/articles/21ec919cf2571a3bba9d988f2ce65fcc.html
                    if (!(ipAddress instanceof Inet4Address) || ipAddress.isLoopbackAddress() || ipAddress.isLinkLocalAddress()) {
                        continue;
                    }

                    String ip = ipAddress.getHostAddress();
                    tmpNetInfos.getIps().add(ip);
                }
                if (tmpNetInfos.getIps().isEmpty()) {
                    continue;
                }

                mapResult.put(networkInterface.getName(), tmpNetInfos);
            }
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return mapResult;
    }

    /**
     * 将MAC地址从字节数组转换为字符串
     * @param macBytes
     * @return
     */
    private static String toMacStr(byte[] macBytes) {
        StringBuilder sb = new StringBuilder();
        for (int i=0; i<macBytes.length; i++) {
            int v = macBytes[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                sb.append("0");
            }
            sb.append(hv);

            if (i < macBytes.length - 1) {
                sb.append("-");
            }
        }
        return sb.toString();
    }

    /**
     * 检测IP端口是否可达
     * @param ip
     * @param port
     * @return
     */
    public static boolean isAvailable(String ip, int port) {
        boolean available = false;
        Socket s = null;

        try {
            s = new Socket();
            s.connect(new InetSocketAddress(ip, port), 3000);
            if (s.isConnected()) {
                available = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (s != null) {
                try {
                    s.close();
                } catch (Exception e) {
                    // ignore.
                }
            }
        }
        return available;
    }

    private static class NetworkInfo {
        private String mac;
        private List<String> ips = new ArrayList<>();

        public NetworkInfo(String mac) {
            this.mac = mac;
        }

        public String getMac() {
            return mac;
        }

        public List<String> getIps() {
            return ips;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(mac);
            sb.append("(");
            for (int i=0; i<ips.size(); i++) {
                sb.append(ips.get(i));
                if (i < ips.size() - 1) {
                    sb.append(", ");
                }
            }
            sb.append(")");
            return sb.toString();
        }
    }
}
